/*
 * txtquery operations with ltree
 * Teodor Sigaev <teodor@stack.net>
 * contrib/ltree/ltxtquery_op.c
 */
#include "postgres.h"

#include <ctype.h>

#include "ltree.h"
#include "miscadmin.h"

PG_FUNCTION_INFO_V1(ltxtq_exec);
PG_FUNCTION_INFO_V1(ltxtq_rexec);

/*
 * check for boolean condition
 */
bool
ltree_execute(ITEM *curitem, void *checkval, bool calcnot, bool (*chkcond) (void *checkval, ITEM *val))
{
    /* since this function recurses, it could be driven to stack overflow */
    check_stack_depth();

    if (curitem->type == VAL)
        return (*chkcond) (checkval, curitem);
    else if (curitem->val == (int32) '!')
    {
        return (calcnot) ?
            ((ltree_execute(curitem + 1, checkval, calcnot, chkcond)) ? false : true)
            : true;
    }
    else if (curitem->val == (int32) '&')
    {
        if (ltree_execute(curitem + curitem->left, checkval, calcnot, chkcond))
            return ltree_execute(curitem + 1, checkval, calcnot, chkcond);
        else
            return false;
    }
    else
    {                            /* |-operator */
        if (ltree_execute(curitem + curitem->left, checkval, calcnot, chkcond))
            return true;
        else
            return ltree_execute(curitem + 1, checkval, calcnot, chkcond);
    }
}

typedef struct
{
    ltree       *node;
    char       *operand;
} CHKVAL;

static bool
checkcondition_str(void *checkval, ITEM *val)
{
    ltree_level *level = LTREE_FIRST(((CHKVAL *) checkval)->node);
    int            tlen = ((CHKVAL *) checkval)->node->numlevel;
    char       *op = ((CHKVAL *) checkval)->operand + val->distance;
    int            (*cmpptr) (const char *, const char *, size_t);

    cmpptr = (val->flag & LVAR_INCASE) ? ltree_strncasecmp : strncmp;
    while (tlen > 0)
    {
        if (val->flag & LVAR_SUBLEXEME)
        {
            if (compare_subnode(level, op, val->length, cmpptr, (val->flag & LVAR_ANYEND)))
                return true;
        }
        else if (
                 (
                  val->length == level->len ||
                  (level->len > val->length && (val->flag & LVAR_ANYEND))
                  ) &&
                 (*cmpptr) (op, level->name, val->length) == 0)
            return true;

        tlen--;
        level = LEVEL_NEXT(level);
    }

    return false;
}

Datum
ltxtq_exec(PG_FUNCTION_ARGS)
{
    ltree       *val = PG_GETARG_LTREE(0);
    ltxtquery  *query = PG_GETARG_LTXTQUERY(1);
    CHKVAL        chkval;
    bool        result;

    chkval.node = val;
    chkval.operand = GETOPERAND(query);

    result = ltree_execute(
                           GETQUERY(query),
                           &chkval,
                           true,
                           checkcondition_str
        );

    PG_FREE_IF_COPY(val, 0);
    PG_FREE_IF_COPY(query, 1);
    PG_RETURN_BOOL(result);
}

Datum
ltxtq_rexec(PG_FUNCTION_ARGS)
{
    PG_RETURN_DATUM(DirectFunctionCall2(ltxtq_exec,
                                        PG_GETARG_DATUM(1),
                                        PG_GETARG_DATUM(0)
                                        ));
}
